// 
// Copyright (C) 2007 Klaus Hansen and Rasmus Halland
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//


#if UNITTEST

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Text.RegularExpressions;
using CellDotNet.Intermediate;
using CellDotNet.Spe;
using NUnit.Framework;

namespace CellDotNet
{
	[TestFixture, Ignore("Only for generating code.")]
	public class CodeGenUtils
	{
		/// <summary>
		/// Returns all IL opcodes.
		/// </summary>
		/// <returns></returns>
		private List<IROpCode> GetILOpcodes()
		{
			List<IROpCode> list=  new List<IROpCode>();
			FieldInfo[] fields = typeof(IROpCodes).GetFields();

			foreach (FieldInfo fi in fields)
			{
				if (fi.FieldType != typeof(IROpCode))
					continue;

				IROpCode oc = (IROpCode)fi.GetValue(null);
				list.Add(oc);
			}

			return list;
		}

		[Test]
		public void GenerateILFlowNextOpCodeSwitchCases()
		{
			TextWriter sw = Console.Out;

			foreach (IROpCode oc in GetILOpcodes())
			{
				if (oc.FlowControl != FlowControl.Next)
					continue;

//				if (oc.OpCodeType == OpCodeType.Macro)
//					continue;

				sw.WriteLine("\t\t\tcase IRCode.{0}: // {1}", oc.IRCode, oc.Name);
			}
		}

		/// <summary>
		/// Generates enumeration values 
		/// </summary>
		[Test]
		public void GenerateILEnumValues()
		{
			StringWriter sw = new StringWriter();

			foreach (IROpCode oc in GetILOpcodes())
			{
//				if (oc.OpCodeType == OpCodeType.Macro)
//					sw.WriteLine("\t\t// {0} = {1}, // {2}", oc.Code, (int) oc.Code, oc.OpCodeType);
//				else
					sw.WriteLine("\t\t{0} = {1},", oc.IRCode, (int) oc.IRCode);
			}

			Console.Write(sw.GetStringBuilder().ToString());
		}

		/// <summary>
		/// Returns the qualified name of the static field that contains the field. 
		/// Used for generating the instruction writer methods.
		/// </summary>
		static string GetQualifiedOpcodeFieldName(SpuOpCode opcode)
		{
			return typeof (SpuOpCode).Name + "." + opcode.Name;
		}

		[Test]
		public void GenerateSpuInstructionWriterMethods()
		{
			StringWriter tw = new StringWriter();

			tw.Write(@"
	// THIS CLASS IS GENERATED BY {0}.{1} - DO NO EDIT. 
	partial class {2}
	{{
", GetType().FullName, "GenerateSpuInstructionWriterMethods()", typeof(SpuInstructionWriter).Name);

			foreach (SpuOpCode opcode in SpuOpCode.GetSpuOpCodes())
			{
				if (opcode.Format == SpuInstructionFormat.Custom)
					continue;

				// capitalized name.
				string ocname = opcode.Name[0].ToString().ToUpper() + opcode.Name.Substring(1);

				List<string> regnames = new List<string>();
				if ((opcode.Parts & SpuInstructionPart.Rt) != SpuInstructionPart.None)
					regnames.Add("rt");
				if ((opcode.Parts & SpuInstructionPart.Ra) != SpuInstructionPart.None)
					regnames.Add("ra");
				if ((opcode.Parts & SpuInstructionPart.Rb) != SpuInstructionPart.None)
					regnames.Add("rb");
				if ((opcode.Parts & SpuInstructionPart.Rc) != SpuInstructionPart.None)
					regnames.Add("rc");


				StringBuilder declnewdest = new StringBuilder();
				StringBuilder declolddest = new StringBuilder();
				StringBuilder bodynewdest = new StringBuilder();
				StringBuilder bodyolddest = new StringBuilder();


				// Declaration.
				foreach (string name in regnames)
				{
					declolddest.Append((declolddest.Length != 0 ? ", " : "") + "VirtualRegister " + name);
					if (name != "rt" || opcode.RegisterRtNotWritten)
						declnewdest.Append((declnewdest.Length != 0 ? ", " : "") + "VirtualRegister " + name);
				}

				if (opcode.HasImmediate)
				{
					declnewdest.Append((declnewdest.Length != 0 ? ", " : "") + "int immediate");
					declolddest.Append((declolddest.Length != 0 ? ", " : "") + "int immediate");
				}

				// Body.
				if ((opcode.Parts & SpuInstructionPart.Rt) != SpuInstructionPart.None)
				{
					if (opcode.RegisterRtNotWritten)
					{
						bodynewdest.AppendLine("AssertRegisterNotNull(rt, \"rt\");");
						bodynewdest.AppendLine("inst.Rt = rt;");
						bodyolddest.AppendLine("AssertRegisterNotNull(rt, \"rt\");");
						bodyolddest.AppendLine("inst.Rt = rt;");
					}
					else
					{
						bodynewdest.AppendLine("inst.Rt = NextRegister();");
						bodyolddest.AppendLine("AssertRegisterNotNull(rt, \"rt\");");
						bodyolddest.AppendLine("inst.Rt = rt;");
					}
				}
				if ((opcode.Parts & SpuInstructionPart.Ra) != SpuInstructionPart.None)
				{
					bodynewdest.AppendLine("inst.Ra = ra;");
					bodynewdest.AppendLine("AssertRegisterNotNull(ra, \"ra\");");
					bodyolddest.AppendLine("inst.Ra = ra;");
					bodyolddest.AppendLine("AssertRegisterNotNull(ra, \"ra\");");
				}
				if ((opcode.Parts & SpuInstructionPart.Rb) != SpuInstructionPart.None)
				{
					bodynewdest.AppendLine("inst.Rb = rb;");
					bodynewdest.AppendLine("AssertRegisterNotNull(rb, \"rb\");");
					bodyolddest.AppendLine("inst.Rb = rb;");
					bodyolddest.AppendLine("AssertRegisterNotNull(rb, \"rb\");");
				}
				if ((opcode.Parts & SpuInstructionPart.Rc) != SpuInstructionPart.None)
				{
					bodynewdest.AppendLine("inst.Rc = rc;");
					bodynewdest.AppendLine("AssertRegisterNotNull(rc, \"rc\");");
					bodyolddest.AppendLine("inst.Rc = rc;");
					bodyolddest.AppendLine("AssertRegisterNotNull(rc, \"rc\");");
				}
				if (opcode.HasImmediate)
				{
					bodynewdest.AppendLine("inst.Constant = immediate;");
					bodyolddest.AppendLine("inst.Constant = immediate;");
				}
				bodynewdest.AppendLine("AddInstruction(inst);");
				bodyolddest.AppendLine("AddInstruction(inst);");
				if (!opcode.RegisterRtNotWritten)
					bodynewdest.AppendLine("return inst.Rt;");

				// Put it together.

				string methodformat = @"
		/// <summary>
		/// {0}
		/// </summary>
		public {3} Write{1}({2})
		{{
			SpuInstruction inst = new SpuInstruction({4});
			{5}
		}}
";
				// GetQualifiedOpcodeFieldName(opcode)
				tw.Write(methodformat, opcode.Title, ocname, declnewdest, opcode.RegisterRtNotWritten ? "void" : "VirtualRegister", GetQualifiedOpcodeFieldName(opcode), bodynewdest);
				if (declolddest.Length != declnewdest.Length)
					tw.Write(methodformat, opcode.Title, ocname, declolddest, "void", GetQualifiedOpcodeFieldName(opcode), bodyolddest);
			}

			tw.Write(@"
	}
");

			Console.Write(tw.GetStringBuilder().ToString());
		}

		[Test, Explicit]
		public void GenerateSpuOpCodeEnum()
		{
			StringWriter sw = new StringWriter();

			sw.Write(@"
	// This enumeration is generated by {0}. DO NOT EDIT.
	enum SpuOpCodeEnum
	{{
		None,", GetType().FullName);

			foreach (SpuOpCode code in SpuOpCode.GetSpuOpCodes())
			{
				sw.Write(@"
		/// <summary>
		/// {1}
		/// </summary>
		{0},", CultureInfo.InvariantCulture.TextInfo.ToTitleCase(code.Name), code.Title);
			}
			sw.Write(@"
	}
");
			Console.Write(sw.GetStringBuilder());
		}

		[Test, Explicit]
		public void GenerateIROpcodes()
		{
			StringBuilder enumcode = new StringBuilder();
			enumcode.AppendFormat(@"
		// These IR opcode enum values are generated by {0}. DO NOT EDIT.
", GetType().FullName);

			StringBuilder opcodewriter = new StringBuilder();
			opcodewriter.AppendFormat(@"
	// This class is generated by {0}. DO NOT EDIT.
	partial class IROpCodes
	{{
", GetType().FullName);

			FieldInfo[] fields = typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static);
			foreach (FieldInfo fi in fields)
			{
				OpCode oc = (OpCode)fi.GetValue(null);

				if (oc.OpCodeType == OpCodeType.Macro)
				{
					//					continue;
					// We generally don't want macros, but these are okay...
					if (oc != OpCodes.Blt && oc != OpCodes.Ble &&
						oc != OpCodes.Blt_Un && oc != OpCodes.Ble_Un &&
						oc != OpCodes.Beq && oc != OpCodes.Bne_Un &&
						oc != OpCodes.Bge && oc != OpCodes.Bgt &&
						oc != OpCodes.Bge_Un && oc != OpCodes.Bgt_Un)
					{
						Console.WriteLine("Skipping opcode: {0}.", oc.Name);
						continue;
					}
				}

				enumcode.AppendFormat("		{0},\r\n", fi.Name);
				opcodewriter.AppendFormat(
					"		public static readonly IROpCode {0} = new IROpCode(\"{1}\", IRCode.{0}, " +
					"FlowControl.{2}, OpCodes.{3});\r\n",
					fi.Name, oc.Name, oc.FlowControl, fi.Name);
			}


			enumcode.Append(@"
		// End of generated opcodes.
");
			opcodewriter.Append(@"
	}
");

			Console.Write(enumcode.ToString());
			Console.Write(opcodewriter.ToString());
		}

		public static void GeneratePatchCode(string fileWithDisassembly, string outputFile, HashSet<string> functionNames)
		{
			Dictionary<int, QuadWord> constants = new Dictionary<int, QuadWord>();

			var instRegex = new Regex(@"^\s+([0-9a-f]+):\s*(\w\w \w\w \w\w \w\w)");

			Console.WriteLine("Reading constants...");
			{
				uint i1 = 0, i2 = 0, i3 = 0;
				int qwStartAddress = 0;
				int linenum = 0;
				foreach (string line in File.ReadAllLines(fileWithDisassembly))
				{
					linenum++;
					var match = instRegex.Match(line);
					if (!match.Success)
						continue;

					int address = Convert.ToInt32(match.Groups[1].Value, 16);
					uint hex = Convert.ToUInt32(match.Groups[2].Value.Replace(" ", ""), 16);

					switch (address % 16)
					{
						case 0:
							i1 = hex;
							qwStartAddress = address;
							break;
						case 4:
							i2 = hex;
							break;
						case 8:
							i3 = hex;
							break;
						case 12:
							if (address == qwStartAddress + 12)
							{
								uint i4 = hex;
								QuadWord qw = new QuadWord(i1, i2, i3, i4);
								constants.Add(qwStartAddress, qw);
							}
							break;
						default:
							throw new Exception();
					}

				}
			}

			Console.WriteLine("Reading disassembly...");
			{
				var headerRegex = new Regex(@"^([0-9a-f]{8}) <(\w+)>:");
				var lqrRegex = new Regex(@"^\s+([0-9a-f]+):.+\slqr\s\$(\d+).+#\s*([0-9a-f]+)");
				int lineno = 0;

				string currentfunctionname = null;
				int? currentfunctionaddress = null;
				File.Delete(outputFile);

				var sw = new StringWriter();
				List<int> currentfunctionbody = null;

				foreach (string line in File.ReadAllLines(fileWithDisassembly))
				{
					lineno++;

					if (line == "")
						continue;

					var h = headerRegex.Match(line);
					var inst = lqrRegex.Match(line);

					if (h.Success)
					{
						if (currentfunctionbody != null && !string.IsNullOrEmpty(currentfunctionname))
						{
							int[] arr = currentfunctionbody.ToArray();
							Utilities.HostToBigEndian(arr);
							byte[] codebytes = new byte[arr.Length * 4];
							Buffer.BlockCopy(arr, 0, codebytes, 0, codebytes.Length);
							File.WriteAllBytes(Path.Combine(Path.GetDirectoryName(outputFile), currentfunctionname + ".bin"), codebytes);
						}

						currentfunctionname = h.Groups[2].Value;
						currentfunctionaddress = Convert.ToInt32(h.Groups[1].Value, 16);
						string outputline = string.Format(@"
					// {0} ", currentfunctionname);

						sw.WriteLine();
						sw.Write(outputline);

						if (functionNames.Contains(currentfunctionname))
							currentfunctionbody = new List<int>();
						else
							currentfunctionbody = null;
					}
					else if (inst.Success)
					{
						if (string.IsNullOrEmpty(currentfunctionname) || currentfunctionaddress == null)
							throw new Exception("xxasdf");

						if (!functionNames.Contains(currentfunctionname))
							continue;

						int instaddress = Convert.ToInt32(inst.Groups[1].Value, 16);
						int regnum = Convert.ToInt32(inst.Groups[2].Value);
						int constaddress = Convert.ToInt32(inst.Groups[3].Value, 16);
						QuadWord fs = constants[constaddress];

						int instoffset = instaddress - currentfunctionaddress.Value;
						string outputline = string.Format(@"
					{0}.Seek(0x{1:x});
					{0}.Writer.WriteLoad(HardwareRegister.GetHardwareRegister({2}), RegisterConstant({3})); // {4}",
							currentfunctionname, instoffset, regnum,
							"0x" + fs.I1.ToString("x") + ", 0x" + fs.I2.ToString("x") + ", 0x" + fs.I3.ToString("x") + ", 0x" + fs.I4.ToString("x"),
							line);

						sw.WriteLine();
						sw.Write(outputline);
					}

					var match2 = instRegex.Match(line);
					if (match2.Success && currentfunctionbody != null)
					{
						uint hex = Convert.ToUInt32(match2.Groups[2].Value.Replace(" ", ""), 16);
						currentfunctionbody.Add((int)hex);
					}
				}

				File.WriteAllText(outputFile, sw.GetStringBuilder().ToString());
			}
		}
	}
}

#endif